/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package GUI;

import MouseSensorDriver.Calibrator;
import MouseSensorDriver.GraphListener;
import datasource.ADCSource;
import datasource.AirshipKitchenSource;
import datasource.DataSource;
import datasource.DynamixelSource;
import datasource.SlideFilter;
import dynamixel.Actuator;
import dynamixel.EndoscopeGear;
import dynamixel.HostActuator;
import dynamixel.ScanListener;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Random;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.ComboBoxModel;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableModel;
import org.min.gui.JSlidePlotter;

/**
 *
 * @author root
 */
public class ActScriptAnalyzer implements TableModel, ComboBoxModel<String>,
        Serializable {

    int filterValADCKitchen = 10;
    int filterValADCAirShip = 1;
    int filterValEngineAirShip = 5;
    int filterValADCClean = 30;
    int filterValEngineClean = 30;
    private int columnCnt = 4;
    final int timeOut = 1300;
    double meanRelError;
    double meanAbsError;
    double SKO;
    int scansCnt = 0;
    int resBufSize = 64;
    int bufSize = 1024;
    boolean airship = true;
    boolean old_airship = true;
    ArrayList<ScanListener> scanList = new ArrayList<>();
    ArrayList<TableModelListener> tml = new ArrayList<>();
    ArrayList<ListDataListener> cb_listeners = new ArrayList<>();
    double[] torque = new double[bufSize];
    double[] time = new double[bufSize];
    double[] adcTorq = new double[bufSize];
    double[] calibEngTorq = new double[bufSize];
    double[] calibADCTorq = new double[bufSize];
    double[][] torqRes = new double[resBufSize][];
    double[][] calibEngRes = new double[resBufSize][];
    double[][] calibADCRes = new double[resBufSize][];
    double[][] timeRes = new double[resBufSize][];
    double[][] adcTorqRes = new double[resBufSize][];
    final String delimSemicolon = ";\t " + (char) 10 + (char) 13;
    StringTokenizer tokenizerSC;
    StringTokenizer tok;
    Actuator[] actuators;
    Random rand = new Random();
    DataSource ds_acts[];
    DataSource ds_adc[];
    SlideFilter filters[];
    SlideFilter engFiltr = new SlideFilter(filterValEngineClean);
    private int it = 0;
    int cb_selitm = -1;
    int engNums[] = new int[resBufSize];
    int adcChNums[] = new int[resBufSize];
    JSlidePlotter engPlot;
    JSlidePlotter adcPlot;
    boolean isScan = false;
    int engCalibCh[][] = {
        {0,0,0,0},
        {},
        {0, 43, 0, 0},
        {},
        {0, 42, 0, 0},
        {},
        {0, 0, 0, 45},
        {},
        {0, 0, 44, 0},
        {},
        {0,0,0,41},
        {},
        {0,39,0,0},
        {},
        {0,0,40,0},
        {},
        {38,0,0,0}
    };

    public ActScriptAnalyzer(Actuator[] actuators) {
        this.actuators = actuators;
        ds_acts = new DataSource[actuators.length];
        ds_adc = new DataSource[16];
        filters = new SlideFilter[16];
        for (int i = 0; i < actuators.length; i++) {
            ds_acts[i] = new DynamixelSource(actuators[i],
                    HostActuator.PRESENT_LOAD_L, 2);
        }
        for (int i = 0; i < 16; i++) {
            filters[i] = new SlideFilter(filterValADCAirShip);
            ds_adc[i] = new AirshipKitchenSource(filterValADCKitchen);
        }
    }

    public void analyzeScript(String source) {
        tokenizerSC = new StringTokenizer(source, delimSemicolon);
        int actNum = 0;
        double torq = 0;
        int goalPos = 0;
        double startTime = 0;
        boolean scanned = false;

        while (tokenizerSC.countTokens() >= 4 && scansCnt < resBufSize) {
            actNum = Integer.valueOf(tokenizerSC.nextToken());
            goalPos = Integer.valueOf(tokenizerSC.nextToken());
            actuators[actNum].setSpeed(Integer.valueOf(tokenizerSC.
                    nextToken()), true);
            actuators[actNum].setTorqueLimit(Integer.valueOf(tokenizerSC.
                    nextToken()));
            int sc_chn = Integer.valueOf(tokenizerSC.nextToken());
            if (old_airship != airship) {
                if (airship) {
                    for (int j = 0; j < 16; j++) {
                        ds_adc[j] = new AirshipKitchenSource(filterValADCKitchen);
                        filters[j] = new SlideFilter(filterValADCAirShip);
                    }
                    engFiltr = new SlideFilter(filterValEngineAirShip);
                } else {
                    for (int j = 0; j < 16; j++) {
                        ds_adc[j] = new ADCSource(j + 1);
                        filters[j] = new SlideFilter(filterValADCClean);
                    }
                    engFiltr = new SlideFilter(filterValEngineClean);
                }
                old_airship = airship;
            }
            if (sc_chn != 0) {
                isScan = true;
                scanned = true;
                engNums[scansCnt] = actNum;
                adcChNums[scansCnt] = sc_chn;
                actuators[actNum].setGoalPosition(goalPos);
                try {
                    Thread.sleep(200);
                } catch (InterruptedException ex) {
                    Logger.getLogger(ActScriptAnalyzer.class.getName()).log(Level.SEVERE, null, ex);
                }
                double initVal = ds_acts[actNum].getData(0, 0);
                filters[sc_chn - 1].initFilter(ds_adc[sc_chn - 1].getData(initVal, 0));
                engFiltr.initFilter(initVal);
                startTime = System.currentTimeMillis();
                while (System.currentTimeMillis() < startTime + timeOut) {
                    if ((torq = ds_acts[actNum].getData(0, 0)) != -1) {
                        torque[it] = torq;
                        time[it] = (System.currentTimeMillis() - startTime) / 1000;
                        /*int depth_gelium = 0;
                         double res = 0;
                         while(depth_gelium<20 && (i-depth_gelium)>=0){
                              res+=torque[i-depth_gelium];
                         depth_gelium++;
                         }
                         res/=(depth_gelium);
                         res+=(1-Math.exp(-Math.abs
                         * (torque[i]-torque[i-depth_gelium+1])))*2
                         *Math.signum(torque[i]-torque[i-depth_gelium+1]);
                         res+= 2 * (rand.nextDouble() - 0.5) + time[i]*0.2;*/
                        adcTorq[it] = ds_adc[sc_chn - 1].getData(torque[it], time[it]);

                        translateToPlots(torq, adcTorq[it] / 4);
                        /*try {
                         Thread.sleep(1);
                         } catch (InterruptedException ex) {
                         Logger.getLogger(ActScriptAnalyzer.class.getName()).
                         log(Level.SEVERE, null, ex);
                         }*/
                        it++;
                    }
                }
                isScan = false;
//                for (int i = 0; i < sensArr.length; i++) {
//                    sensArr[i] = 0 ;
//                }

                for (int i = 0; i < it; i++) {
                    torque[i] = engFiltr.filter(torque[i]);
                    calibEngTorq[i] = getCupboardTorqueEng(actNum, sc_chn,
                            torque[i]);
                    adcTorq[i] = filters[sc_chn - 1].filter(adcTorq[i]);
                    calibADCTorq[i] = getCupboardTorqueADC(sc_chn, adcTorq[i]);
                }
                torqRes[scansCnt] = new double[it];
                timeRes[scansCnt] = new double[it];
                adcTorqRes[scansCnt] = new double[it];
                calibEngRes[scansCnt] = new double[it];
                calibADCRes[scansCnt] = new double[it];

                addResults(torque, torqRes, scansCnt, it);
                addResults(time, timeRes, scansCnt, it);
                addResults(adcTorq, adcTorqRes, scansCnt, it);
                addResults(calibEngTorq, calibEngRes, scansCnt, it);
                addResults(calibADCTorq, calibADCRes, scansCnt, it);

                scanned = false;
                scansCnt++;
                it = 0;

            } else {
                actuators[actNum].setGoalPosition(goalPos);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException ex) {
                    Logger.getLogger(ActScriptAnalyzer.class.getName()).
                            log(Level.SEVERE, null, ex);
                }
                while (actuators[actNum].isMoving()) {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException ex) {
                        Logger.getLogger(ActScriptAnalyzer.class.getName()).
                                log(Level.SEVERE, null, ex);
                    }
                }
            }
        }
        tokenizerSC = null;
    }

    public void addResults(double dataFrom[], double dataTO[][], int j, int i) {
        System.arraycopy(dataFrom, 0, dataTO[j], 0, i);
        changed();
    }

    public double[] getCalibADCTorque(int scanN) {
        return (scanN < resBufSize && scanN > -1) ? calibADCRes[scanN] : null;
    }

    public double[] getCalibTorque(int scanN) {
        return (scanN < resBufSize && scanN > -1) ? calibEngRes[scanN] : null;
    }

    public double[] getTorque(int scanN) {
        return (scanN < resBufSize && scanN > -1) ? torqRes[scanN] : null;
    }

    public double[] getTime(int scanN) {
        return (scanN < resBufSize && scanN > -1) ? timeRes[scanN] : null;
    }

    public double[] getADCTorque(int scanN) {
        return (scanN < resBufSize && scanN > -1) ? adcTorqRes[scanN] : null;
    }

    @Override
    public int getRowCount() {
        return scansCnt;
    }

    @Override
    public int getColumnCount() {
        return this.columnCnt;
    }

    @Override
    public String getColumnName(int i) {
        switch (i) {
            case 0: {
                return "№";
            }
            case 1: {
                return "СКО";
            }
            case 2: {
                return "Средняя абсолютная погрешность";
            }
            case 3: {
                return "Средняя относительная погрешность";
            }
            default:
                return "";
        }
    }

    @Override
    public Class<?> getColumnClass(int i) {
        return String.class;
    }

    @Override
    public boolean isCellEditable(int i, int i1) {
        return false;
    }

    @Override
    public Object getValueAt(int i, int i1) {
        switch (i1) {
            case 0: {
                return "" + (i + 1);
            }
            case 1: {
                double sko = getSigma(i);
                if (sko == -1) {
                    return "none";
                } else {
                    return sko;
                }
            }
            case 2: {
                return getMeanAbsError(i);
            }
            case 3: {
                return getMeanRelError(i);
            }
            default:
                return "";
        }
    }

    @Override
    public void setValueAt(Object o, int i, int i1) {
    }

    @Override
    public void addTableModelListener(TableModelListener tl) {
        tml.add(tl);
    }

    @Override
    public void removeTableModelListener(TableModelListener tl) {
        tml.remove(tl);
    }

    private void changed() {
        for (int i = 0; i < tml.size(); i++) {
            tml.get(i).tableChanged(new TableModelEvent(this));
        }
        for (int i = 0; i < cb_listeners.size(); i++) {
            cb_listeners.get(i).contentsChanged(
                    new ListDataEvent(this,
                    ListDataEvent.CONTENTS_CHANGED, 0, scansCnt - 1));
        }
    }

    public double getMaxAbsError(int scanNum) {
        if (scanNum > -1) {
            double torq[] = getTorque(scanNum);
            double adcTorq[] = getADCTorque(scanNum);
            double max = Math.abs(torq[0] - adcTorq[0]);
            for (int i = 1; i < torq.length; i++) {
                if (max < Math.abs(torq[i] - adcTorq[i])) {
                    max = Math.abs(torq[i] - adcTorq[i]);
                }
            }
            return max;
        } else {
            return -1;
        }
    }

    public double getMeanAbsError(int scanNum) {
        if (scanNum < resBufSize && scanNum > -1) {
            double sum = 0;
            for (int i = 0; i < calibEngRes[scanNum].length; i++) {
                sum += Math.abs(calibEngRes[scanNum][i] - calibADCRes[scanNum][i]);
            }
            return sum / calibEngRes[scanNum].length;
        } else {
            return -1;
        }
    }

    public double getMeanRelError(int scanNum) {
        if (scanNum < resBufSize && scanNum > -1) {
            double sum = 0;
            for (int i = 0; i < calibEngRes[scanNum].length; i++) {
                sum += Math.abs((calibEngRes[scanNum][i] - calibADCRes[scanNum][i])
                        / calibADCRes[scanNum][i]);
            }
            return Math.abs(sum / calibEngRes[scanNum].length);
        } else {
            return -1;
        }
    }

    public double getSigma(int scanNum) {
        if (scanNum < resBufSize && scanNum > -1) {
            double sum = 0;
            double val = 0;
            for (int i = 0; i < calibEngRes[scanNum].length; i++) {
                val = calibEngRes[scanNum][i] - calibADCRes[scanNum][i];
                sum += val * val / calibEngRes[scanNum].length;
            }
            return Math.sqrt(sum);
        } else {
            return -1;
        }
    }

    public void setAirship(boolean airship) {
        this.airship = airship;
    }

    public void addScanListener(ScanListener sl) {
        scanList.add(sl);
    }

    public void removeScanListener(int indx) {
        scanList.remove(indx);
    }

    public void clearScanList() {
        scanList.clear();
    }

    public double getCurrentADCVal() {
        return calibADCTorq[it];
    }

    public double getCurrentEngVal() {
        return calibEngTorq[it];
    }

    @Override
    public void setSelectedItem(Object anItem) {
        try {
            cb_selitm = Integer.valueOf(((String) anItem).substring(1)) - 1;
        } catch (Exception ex) {
            System.err.println("err in setSelItm ActSA. Itm:" + ((String) anItem));
        }
    }

    @Override
    public Object getSelectedItem() {
        return getElementAt(cb_selitm);
    }

    @Override
    public int getSize() {
        return scansCnt;
    }

    @Override
    public String getElementAt(int index) {
        if (index == -1) {
            return "";
        }
        return "#" + (index + 1);
    }

    @Override
    public void addListDataListener(ListDataListener l) {
        cb_listeners.add(l);
    }

    @Override
    public void removeListDataListener(ListDataListener l) {
        cb_listeners.remove(l);
    }

    public double getCupboardTorqueEng(int actNum, int chNum, double val) {
        int engCh = getEngCalibChannel(actNum, chNum);
        return Calibrator.get().getData(engCh, (long) val);
    }

    public double getCupboardTorqueADC(int chNum, double val) {
        int adcCh = getADCCalibChannel(chNum);
        return Calibrator.get().getData(adcCh, (long) val);
    }

    public int getEngCalibChannel(int actNum, int chNum) {
        //return actNum + Calibrator.ENDO_SENSOR_1_ENG ;
        System.out.println("Chnum: " + chNum  + " actNum: " + actNum);
        return engCalibCh[chNum][actNum];
    }

    public int getADCCalibChannel(int chNum) {
        return chNum / 2 + Calibrator.ENDO_SENSOR_1_ADC - 1;
    }

    public int[] getEngNums() {
        return engNums;
    }

    public int[] getADCChNums() {
        return adcChNums;
    }

    public boolean isEmptyScanList() {
        return scanList.isEmpty();
    }

    public void setEngPlotter(JSlidePlotter engPlot) {
        this.engPlot = engPlot;
    }

    public void setADCPlotter(JSlidePlotter adcPlot) {
        this.adcPlot = adcPlot;
    }

    public void translateToPlots(double torq, double adc) {
        for (int i = 0; i < scanList.size(); i++) {
            scanList.get(i).addValue(torq, adc);
        }
    }
}